package com.qunar.train.mock.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by hongzhe.yao on 2018/7/3. ：根据xml文件自动生成mock实现类和配置文件 modified by xudong.tai on 2018/7/9 ：
 * 1，自动搜索main目录xml文件并解析dubbo接口服务；2，xml文件解析方法修改为字符串处理，提高性能 modified by zhangqianqian.zhang on
 * 2018/5/28 : 1.保留旧版dubbo的所有方法，添加isNewCode开关，默认使用新版；2.新增新版方法，实现逻辑首先生成字符串类型Class内容,存入静态Map（stringTypeClasses）中，在生成文件，方便管理
 */
public class GenerateInterfaceImpl {
    private static String BEANPATH = "/src/test/resources/aegis/dubbo/";

    public static GenerateInterfaceImpl instance = new GenerateInterfaceImpl();
    private static boolean status = false;

    //针对旧的代码设置开关，新代码采用@AutoGenerator注解形式生成Dubbo文件，旧代码直接生成在Dubbo目录下
    private static boolean isNewCode = true;
    public static Map<String, String> stringTypeClasses;

    //白名单有顺序依赖，修改为ArrayList
    private static ArrayList<WhiteList> whitelist = new ArrayList<>();

    private static String resPath = "/src/test/java/";
    private String basepath = "";
    private String packageName = "";
    public Map<String, Bean> interfaceInfo = new HashMap<String, Bean>();
    private String clsType = ""; //处理class类型，class or interface

    /**
     * constructor
     */
    public GenerateInterfaceImpl() {
    }

    /**
     * type reFormat
     */
    static {
        whitelist.add(new WhiteList("[[B", "byte[][]"));
        whitelist.add(new WhiteList("[B", "byte[]"));
        whitelist.add(new WhiteList("[[I", "int[][]"));
        whitelist.add(new WhiteList("[I", "int[]"));
        whitelist.add(new WhiteList("$", "."));
        whitelist.add(new WhiteList("class ", ""));
        whitelist.add(new WhiteList("[L", ""));
        whitelist.add(new WhiteList("java.util.Map.java.util.Map", "java.util.Map"));
        whitelist.add(new WhiteList("interface ", ""));
        whitelist.add(new WhiteList(";", "[]"));
    }

    static {
        instance.init();
        instance.getInterfaceInfo();
        instance.interfaceInfoUniqByclazz(instance.interfaceInfo);
    }

    /**
     * define WhiteList structure
     */
    static class WhiteList {
        String ori;
        String des;

        WhiteList(String s1, String s2) {
            ori = s1;
            des = s2;
        }
    }

    /**
     * inner data structure, with method info
     */
    static class Function {
        String name;
        String returns;
        String params;
        String memberName;
        String memberDefault;
        Method method;

        Function() {
            name = "";
            returns = "";
            params = "";
            memberName = "";
            memberDefault = "null";
            method = null;
        }
    }

    /**
     * inner data structure, with interface info
     */
    public static class Bean {
        public String id;
        public String version;
        public String interf;
        public String clazz;

        Bean() {
            id = "";
            version = "";
            interf = "";
            clazz = "";
        }
    }

    /**
     * 字典升序排序
     */
    static class SortByName implements Comparator {
        public int compare(Object o1, Object o2) {
            String s1 = (String) o1;
            String s2 = (String) o2;
            return s1.compareTo(s2);
        }
    }

    /**
     * 【api】
     * <p>
     * 对外全部interface生效接口 for all dubbo interfaces in one module
     */
    public static void generateDubboInterfaceImpl(Boolean isUpdate) {
        if (status) return;
        instance.init();
        instance.getInterfaceInfo();
        instance.interfaceInfoUniqByclazz(instance.interfaceInfo);

        int calls = 0;
        try {
            Iterator<Map.Entry<String, Bean>> entries = instance.interfaceInfo.entrySet().iterator();
            while (entries.hasNext()) {
                Map.Entry<String, Bean> entry = entries.next();
                Bean bean = entry.getValue();
                if (isUpdate && !instance.isVersionStatusChanged(bean.version, bean.clazz)) {
                    continue;
                }
                instance.generateSingleInterfaceImpl(bean.interf, bean.version, bean.clazz);
                AegisLog.info("generateDubboInterfaceImpl: id=" + bean.id + " ,class=" + bean.clazz + " ,version=" + bean.version);
                calls++;
            }
        } catch (Exception e) {
            AegisLog.error(e);
        }
        if (calls > 0) {
            instance.makeBeanFile("");
        }

        status = true;
    }

    public static void generateDubboInterfaceImplNew(Boolean isUpdate) {
        if (status) return;
        stringTypeClasses = instance.generateDubboInterfaceStringImpl(isUpdate);
        if (instance.interfaceInfo.isEmpty()) return;
        instance.makeBeanFile("$Impl");
        instance.generateDubboInterfaceImplFile();
        instance.deleteOldDubboInterfaceImplFile();
        status = true;
    }

    public static void deleteOldDubboInterfaceImplFile() {
        File dubboDic = new File(instance.resPath);
        if (!dubboDic.isDirectory()) return;
        File[] mockDubboFiles = dubboDic.listFiles();
        if (mockDubboFiles == null) return;
        int count = 0;
        for (File file : mockDubboFiles) {
            Boolean isFileShouldExist = false;
            for (Bean bean : instance.interfaceInfo.values()) {
                if (file.isFile()) {
                    String fileSimpleName = file.getName().substring(0, file.getName().lastIndexOf("."));
                    if (bean.clazz.contains(fileSimpleName)) {
                        isFileShouldExist = true;
                    }
                }
            }
            if (!isFileShouldExist) {
                file.delete();
                count++;
            }
        }
        AegisLog.println("================================== 共删除" + count + "个旧的MockDubbo类 ==================================");
    }

    public static void generateDubboInterfaceImplFile() {

        int i = 0;
        for (Map.Entry<String, String> entry : stringTypeClasses.entrySet()) {
            String path = instance.resPath + "/" + entry.getKey() + ".java";
            File file = new File(path);
            file.getParentFile().mkdirs();
            FileWriter fileWriter = null;

            try {
                if (!file.exists()) {
                    file.createNewFile();
                }
                fileWriter = new FileWriter(file, false);
            } catch (IOException e) {
                AegisLog.error(e);
            }
            PrintWriter pw = new PrintWriter(fileWriter, false);
            pw.write(entry.getValue());
            pw.flush();
            pw.close();
            i++;
        }
        AegisLog.println("================================== 共生成" + i + "个MockDubbo类 ==================================");
    }

    public static Map<String, String> generateDubboInterfaceStringImpl(Boolean isUpdate) {

        Map<String, String> stringTypeClasses = new HashMap<>();
        instance.init();
        instance.getInterfaceInfo();
        instance.interfaceInfoUniqByclazz(instance.interfaceInfo);

        int calls = 0;
        try {
            Iterator<Map.Entry<String, Bean>> entries = instance.interfaceInfo.entrySet().iterator();
            while (entries.hasNext()) {
                Map.Entry<String, Bean> entry = entries.next();
                Bean bean = entry.getValue();
                if (isUpdate && !instance.isVersionStatusChanged(bean.version, bean.clazz)) {
                    continue;
                }
                String stringClass = instance.generateSingleInterfaceStringImpl(bean.interf, bean.version, bean.clazz).toString();
                stringTypeClasses.put(bean.clazz.substring(bean.clazz.lastIndexOf(".") + 1), stringClass);
                AegisLog.info("generateDubboInterfaceImpl: id=" + bean.id + " ,class=" + bean.clazz + " ,version=" + bean.version);
                calls++;
            }
        } catch (Exception e) {
            AegisLog.error(e);
        }
        if (calls > 0) {
//            instance.makeBeanFile();
        }
        return stringTypeClasses;
    }

    /**
     * 【api】
     * <p>
     * 对外单个class或者interface实现接口 execute one dubbo interface-impl
     */
    public static void generateSingleDubboInterfaceImpl(Class clazz) {
        if (!clazz.isInterface()) instance.clsType = "class";
        instance.init();

        instance.resPath = instance.resPath.split("/mockDUBBO")[0].split("\\\\mockDUBBO")[0];
        instance.packageName = instance.resPath.substring(instance.resPath.lastIndexOf("com")).replace("\\", ".").replace("/", ".");
        instance.resPath = instance.resPath + '/';

        String version = "1.0";
        for (Bean bean : instance.interfaceInfo.values()) {
            if (bean.interf.compareTo(clazz.getName()) == 0) {
                version = bean.version;
                break;
            }
        }
        instance.generateSingleInterfaceImpl(clazz.getName(), version, "");
    }

    /**
     * set path and package
     */
    private void init() {
        //对于多工程同时创建的maven项目，通过file方式获取路径不正确
        //File file = new File("");
        try {
            instance.basepath = Class.class.getClass().getResource("/").getPath().split("/target")[0];
            List mockDirList = new ArrayList();
            FileAndDirSearch.findDirs(instance.basepath + "/src/test/java", "mockDUBBO", mockDirList);
            if (mockDirList.isEmpty()) {
                throw new NullPointerException("mockDirList is null!");
            } else {
                instance.resPath = mockDirList.get(0).toString();
                instance.mkdirs(instance.resPath);
            }
            instance.packageName = instance.resPath.substring(instance.resPath.lastIndexOf("com")).replace("\\", ".").replace("/", ".");
            instance.resPath = instance.resPath + '/';
        } catch (Exception e) {
            AegisLog.error(e);
        }
    }

    /**
     * mkdirs
     */
    private void mkdirs(String path) {
        File dir = new File(path);
        try {
            if (!dir.exists()) {
                dir.mkdirs();
            }
        } catch (Exception e) {
            AegisLog.error(e);
        }
    }

    /**
     * 解决类名相同包名不同问题，类名相同会生成一份mock类，存在覆盖 对包名不同类名相同的接口，MOck类命名采用id
     */
    private Map<String, Bean> interfaceInfoUniqByclazz(Map<String, Bean> map) {
        List<Map.Entry<String, Bean>> list = new LinkedList<Map.Entry<String, Bean>>(map.entrySet());
        int len = list.size();
        Set<Integer> set = new HashSet<>();

        for (int i = 0; i < len - 1; i++) {
            for (int j = i + 1; j < len; j++) {
                if (list.get(i).getValue().clazz.equals(list.get(j).getValue().clazz) &&
                        !list.get(i).getValue().interf.equals(list.get(j).getValue().interf)) {
                    set.add(i);
                    set.add(j);
                }
            }
        }

        Map<String, Bean> result = new HashMap<String, Bean>();
        if (set.size() == 0) {
            return result;
        }
        for (int n = 0; n < len; n++) {
            Map.Entry<String, Bean> m = list.get(n);
            Bean b = m.getValue();
            if (set.contains(n)) {
                b.clazz = b.clazz.substring(0, b.clazz.lastIndexOf(".")) + ".Mock" + b.id.substring(0, 1).toUpperCase() + b.id.substring(1);
            }
            result.put(m.getKey(), b);
        }
        instance.interfaceInfo = result;
        return result;
    }

    /**
     * return a Class Object base to a filename(like:com.qunar.train.Test)
     */
    private Class<?> getClass(String clsName) {
        Class<?> cls = null;
        try {
            cls = Class.forName(clsName);
        } catch (ClassNotFoundException e) {
            AegisLog.error(e);
            AegisLog.info("error:getClass failed");
            return null;
        }
        return cls;
    }

    /**
     *
     */
    private boolean isInterface(Class<?> clazz) {
        if (!clazz.isInterface()) {
            //AegisLog.info("info: not a interface!");
            return false;
        }
        return true;
    }

    /**
     * is version changed or not
     */
    private boolean isVersionStatusChanged(String version, String clsName) {
        if (version.compareTo(instance.getVersion(clsName)) == 0) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * return interface impl version
     */
    private String getVersion(String clsName) {
        Class<?> cls = null;
        String v = "";
        try {
            cls = Class.forName(clsName);
            Field f = cls.getDeclaredField("VERSION");
            v = f.get(cls).toString();
        } catch (Exception e) {
            return "0.0";
        }
        return v;
    }

    /**
     * get interface data, in workspace, parse all xml files to gather interfaces
     */
    private void getInterfaceInfo() {
        try {
            List xmlFileList = new ArrayList();
            List list = new ArrayList();
            FileAndDirSearch.findFiles(instance.basepath + "/src/main/resources", "mapper", "*.xml", xmlFileList);
            for (int i = 0; i < xmlFileList.size(); i++) {
                BufferedReader br = new BufferedReader(new FileReader(xmlFileList.get(i).toString()));
                String pline = new String("");
                String dubbointerf = new String("");
                boolean flag = false;
                while ((pline = br.readLine()) != null) {
                    pline = " " + pline.trim();
                    if (pline.indexOf(" <dubbo:reference") != -1) {
                        if (pline.indexOf("<!--") != -1 || pline.indexOf("//") != -1) continue;
                        flag = true;
                    }
                    if (flag && (pline.indexOf("</dubbo:reference>") != -1 || pline.indexOf("/>") != -1)) {
                        dubbointerf += pline;
                        list.add(dubbointerf);
                        dubbointerf = "";
                        flag = false;
                    }
                    if (flag) {
                        dubbointerf += pline;
                    }
                }
                br.close();
            }
            for (int i = 0; i < list.size(); i++) {
                Bean bean = strTransToBean(list.get(i).toString());
                bean.clazz = instance.packageName + ".Mock" + bean.interf.substring(bean.interf.lastIndexOf(".") + 1);
                instance.interfaceInfo.put(bean.id, bean);
            }
        } catch (Exception e) {
            AegisLog.error(e);
        }
    }

    /**
     * interface info to Bean Object
     */
    private Bean strTransToBean(String str) {
        Bean bean = new Bean();
        try {
            bean.id = str.split(" id")[1].split("\"")[1];
            bean.interf = str.split(" interface")[1].split("\"")[1];
            bean.version = str.split(" version")[1].split("\"")[1];
        } catch (Exception e) {
            AegisLog.error(e);
        }
        return bean;
    }

    /**
     * return  and paramters type reFormat, make it correct. (if your jdk is 1.8, try to discard it
     * and use Parameter type)
     */
    private String typeFormat(String s) {
        for (int i = 0; i < whitelist.size(); i++) {
            WhiteList wl = whitelist.get(i);
            s = s.replace(wl.ori, wl.des);
        }
        return s;
    }

    /**
     * 泛型返回类型计算
     */
    private String typeFormat(Class<?> returnType, Type genericReturnType) {
        String res = "";
        if (genericReturnType instanceof TypeVariable) {
            if (returnType.getName().equals("java.lang.Object")) {
                res = " <T>";
            } else {
                res = "<T extends " + returnType.getName() + ">";
            }
        }
        return res;
    }

    /**
     * rename function name whith paramters to ensure unique
     */
    private String renameFunc(Method method) {
        String res = method.getName() + "ReturnValue";
        Class<?>[] pts = method.getParameterTypes();

        if (pts.length == 0) {
            return res += "PVoid";
        }

        res += "P";
        StringBuffer buf = new StringBuffer();
        for (Class<?> c : pts) {
            String s = c.getName();
            s = instance.typeFormat(s);
            s = s.replace("[]", "s");//array 2 s
            s = s.substring(s.lastIndexOf('.') + 1).toLowerCase();
            s = s.substring(0, 1).toUpperCase() + s.substring(1);
            buf.append(s);
        }
        res = res + buf.toString();
        return res;
    }

    /**
     * set member default value, for return functions
     *
     * @return dedault value
     */
    private String defaultValue(String s) {
        String res = null;
        String tmp = s;
        if (s.indexOf('.') > 0)
            tmp = s.substring(s.lastIndexOf('.') + 1);

        switch (s) {
            case "int":
            case "byte":
            case "short":
            case "char":
                res = "0";
                break;
            case "long":
                res = "0L";
                break;
            case "float":
                res = "0.0f";
                break;
            case "double":
                res = "0.0d";
                break;
            case "String":
                res = "\"\"";
                break;
            case "boolean":
                res = "false";
                break;
            case "BigDecimal":
            case "Long":
            case "Integer":
                res = tmp + "(0)";
                break;
        }
        return res;
    }

    /**
     * make file: interfaceImpl or mockClass 支持泛型
     */
    private void makeImplFile(Class<?> clazz, Map<String, Function> funcInfo, String version, String mockName) {
        String clsName = clazz.getName();
        String interfaceName = clsName;
        if (mockName.equals("")) {
            mockName = "Mock" + clsName.substring(clsName.lastIndexOf('.') + 1);
        } else {
            mockName = mockName.substring(mockName.lastIndexOf(".") + 1);
        }

        StringBuilder meminfo = new StringBuilder("\n");
        StringBuilder instanceinfo = new StringBuilder("\n");
        StringBuilder funinfo = new StringBuilder("\n");
        StringBuilder setinfo = new StringBuilder("\n");
        StringBuilder clearinfo = new StringBuilder("\n");
        clearinfo.append("\tpublic static void clear() {\n");

        /** 用threadlocal代替instance，解决并发不稳定问题 */
        //meminfo.append("\tpublic static ").append(mockName).append(" instance = new ").append(mockName).append("();\n\n");
        meminfo.append("\tpublic static ThreadLocal<").append(mockName).append("> threadLocal = new ThreadLocal<>();\n");
        instanceinfo.append("\tpublic static ").append(mockName).append(" getInstance() {\n").
                append("\t\tif(threadLocal.get() == null){\n").
                append("\t\t\tthreadLocal.set(new ").append(mockName).append("());\n").
                append("\t\t}\n").
                append("\t\treturn threadLocal.get();\n").append("\t}\n\n");

        /** super class 构造函数mock */
        StringBuilder innerinfo = instance.getInfoInnerClass(clazz, mockName, interfaceName);
        Boolean isLibClass = false;
        if (instance.clsType.equals("class")) isLibClass = instance.isLibClass(interfaceName);

        List<Object> funckey = Arrays.asList(funcInfo.keySet().toArray());
        Collections.sort(funckey, new SortByName());

        String modifyInfo = "";
        String returnInfoF = "";
        boolean hasT = false;
        for (int i = 0; i < funckey.size(); i++) {
            Function f = funcInfo.get(funckey.get(i));

            Pattern p = Pattern.compile("\\WT\\W");
            Matcher m1 = p.matcher(f.params);
            Matcher m2 = p.matcher(f.returns);
            if (m1.find() || m2.find()) {
                modifyInfo = " <T>";
                hasT = true;
            } else {
                modifyInfo = "";
            }

            //判断是否是static方法
            String funchead = "\tpublic";
            if (instance.parseModifier(f.method.getModifiers()).indexOf("public static") > -1) {
                funchead = "\tpublic static";
            }

            if (f.returns.equals("void")) {
                funinfo.append(funchead).append(modifyInfo).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
                        append("){}\n\n");
            } else {
                if (f.returns.equals("T")) {
                    returnInfoF = "return (T) instance.";
                    //modifyInfo = " <T>";
                    modifyInfo = instance.typeFormat(f.method.getReturnType(), f.method.getGenericReturnType());
                } else {
                    returnInfoF = "return instance.";
                }
                funinfo.append(funchead).append(modifyInfo).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
                        append("){\n\t\t").append(returnInfoF).append(f.memberName).append(";\n\t}\n\n");
                meminfo.append("\tprivate ").append(f.returns).append(" ").append(f.memberName).append(" = ").
                        append(f.memberDefault).append(";\n");
                setinfo.append("\tpublic static").append(modifyInfo).append(" void set").
                        append(f.memberName.substring(0, 1).toUpperCase() + f.memberName.substring(1)).
                        append("(").append(f.returns).append(" p){\n\t\tinstance.").append(f.memberName).append(" = p;\n\t}\n\n");
                clearinfo.append("\t\tinstance.").append(f.memberName).append(" = ").append(f.memberDefault).append(";\n");
            }
        }
        clearinfo.append("\t}\n\n");

        if (isLibClass) {
            innerinfo.deleteCharAt(innerinfo.lastIndexOf("}"));
            innerinfo.append(funinfo.toString().replace("public", "@Mock public").replace(" static", "").replace("\n\t", "\n\t\t").replace("instance", "getInstance()"));
            innerinfo.append("\t}\n");
        }

        File file = new File(instance.resPath + mockName + ".java");
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file, false);
            PrintWriter pw = new PrintWriter(fileWriter, false);
            pw.println("package " + instance.packageName + ";\n");
            if (hasT) {
                mockName += "<T>";
            }

            pw.println(instance.getInfoImport());
            pw.println(instance.getInfoAnnotation());

            if (instance.isInterface(clazz)) {
                pw.println("public class " + mockName + " implements " + interfaceName + " {\n");
            } else if (!instance.isNeedExtends(clazz)) {
                pw.println("public class " + mockName + " {\n");
            } else {
                pw.println("public class " + mockName + " extends " + interfaceName + " {\n");
            }
            pw.println("\tpublic static String VERSION = \"" + version + "\";\n");
            pw.println(meminfo.toString());
            pw.println(instanceinfo.toString());
            pw.println(innerinfo.toString());
            pw.println(funinfo.toString().replace("instance", "getInstance()"));
            pw.println(setinfo.toString().replace("instance", "getInstance()"));
            pw.println(clearinfo.toString().replace("instance", "getInstance()"));
            pw.println("}");
            pw.close();
        } catch (Exception e) {
            AegisLog.error(e);
        }
    }

    /**
     * make StringTypeClass: interfaceImpl or mockClass 支持泛型
     */
    private StringBuffer makeImplStringTypeClass(Class<?> clazz, Map<String, Function> funcInfo, String version, String mockName) {
        //修改为生成字符串的形式
        String clsName = clazz.getName();
        String interfaceName = clsName;
        if (mockName.equals("")) {
            mockName = "Mock" + clsName.substring(clsName.lastIndexOf('.') + 1);
        } else {
            mockName = mockName.substring(mockName.lastIndexOf(".") + 1);
        }

        StringBuilder setinfo = new StringBuilder("\n");
        StringBuilder clearinfo = new StringBuilder("\n");
        clearinfo.append("\tpublic static void clear() {}\n");

        List<Object> funckey = Arrays.asList(funcInfo.keySet().toArray());
        Collections.sort(funckey, new SortByName());

        String modifyInfo = "";
        boolean hasT = false;
        for (int i = 0; i < funckey.size(); i++) {
            Function f = funcInfo.get(funckey.get(i));

            Pattern p = Pattern.compile("\\WT\\W");
            Matcher m1 = p.matcher(f.params);
            Matcher m2 = p.matcher(f.returns);
            if (m1.find() || m2.find()) {
                modifyInfo = " <T>";
                hasT = true;
            } else {
                modifyInfo = "";
            }

            if (f.returns.equals("void")) {
//                funinfo.append(funchead).append(modifyInfo).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
//                        append("){}\n\n");
            } else {
                String setMethodName = "set" + f.memberName.substring(0, 1).toUpperCase() + f.memberName.substring(1);
                setinfo.append("\tpublic static").append(modifyInfo).append(" void ").append(setMethodName).
                        append("(").append(f.returns).append(" p){");
                setinfo.append("\n\t\t" + mockName + "$Impl." + setMethodName + "(p);\n\t}\n\n");
            }
        }


        StringBuffer stringClass = new StringBuffer();
        stringClass.append("package " + instance.packageName + ";\n\n");
        stringClass.append("public class " + mockName + " {\n");
        stringClass.append(setinfo.toString());
        stringClass.append(clearinfo.toString());
        stringClass.append("}");

        return stringClass;
    }


    /**
     * import contents
     */
    private String getInfoImport() {
        StringBuilder info = new StringBuilder("import java.util.*;\n");
        if (instance.clsType.equals("class")) {
            info.append("import mockit.Mock;\n");
            info.append("import mockit.MockUp;\n");
        }
        return info.toString();
    }

    /**
     * 禁止修改注释声明
     */
    private String getInfoAnnotation() {
        StringBuilder info = new StringBuilder("/**\n");
        info.append(" * 【禁止修改】\n").
                append(" *\n").
                append(" * 本Class系自动实现，请不要修改。\n").
                append(" * 自动生成是采用全量覆盖模式，文件自动更新时，会全部覆盖，个人修改的code都会被丢弃。\n").
                append(" *\n").
                append(" */");
        return info.toString();
    }

    /**
     * 生成函数参数字符串 jdk1.8可直接用Parameter对象
     */
    private String getInfoParameters(Type[] types) {
        String res = "";
        if (types.length != 0) {
            StringBuffer buf = new StringBuffer();
            int i = 0;
            for (Type type : types) {
                buf.append(instance.typeFormat(type.toString()) + " arg" + i++ + ", ");
            }
            res += buf.toString();
            res = res.substring(0, res.length() - 2);
        }
        return res;
    }

    /**
     * 继承类构造函数mock
     */
    private StringBuilder getInfoInnerClass(Class<?> clazz, String mockName, String interfaceName) {
        StringBuilder info = new StringBuilder("");
        /** super class 构造函数mock */
        if (instance.clsType.equals("class")) {
            info.append("\tpublic static class ").append(mockName.substring(4)).
                    append("Mock extends MockUp<").append(interfaceName).append("> {\n");
            Constructor[] constructors = clazz.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                info.append("\t\t@Mock\n\t\t");
                info.append(parseModifier(constructor.getModifiers())).append(" void $init(");
                info.append(instance.getInfoParameters(constructor.getGenericParameterTypes())).append(") {}\n");
            }
            info.append("\t\tpublic static void clear(){}\n");
            info.append("\t}\n\n");
        }
        return info;
    }

    /**
     * 判断类是否不是spring配置注入， 如果不是测试依赖lib类，不需要继承且实现函数写进mockup类里
     */
    private Boolean isLibClass(String interfaceNmae) {
        Boolean res = true;
        try {
            List xmlFileList = new ArrayList();
            FileAndDirSearch.findFiles(instance.basepath + "/src/main/", "mapper", "*.xml", xmlFileList);
            for1:
            for (int i = 0; i < xmlFileList.size(); i++) {
                if (!xmlFileList.get(i).toString().contains("resources") && !xmlFileList.get(i).toString().contains("profiles"))
                    continue;
                BufferedReader br = new BufferedReader(new FileReader(xmlFileList.get(i).toString()));
                String pline = "";
                for2:
                while ((pline = br.readLine()) != null) {
                    if (pline.contains(interfaceNmae)) {
                        res = false;
                        AegisLog.info("info: find class in resources");
                        break for1;
                    }
                }
                br.close();
            }
        } catch (Exception e) {
            AegisLog.error(e);
        }
        return res;
    }

    /**
     * 判断类的mock是否需要继承方式, 根据被mock类的修饰符决定
     */
    private Boolean isNeedExtends(Class<?> clazz) {
        Boolean res = false;
        String modifier = instance.parseModifier(clazz.getModifiers());
        if (modifier.equals("public")) res = true;
        return res;
    }

    /**
     * make file: dubbo.xml
     */
    private void makeBeanFile(String LastLnDex) {
        List<Object> interfaceinfokey = Arrays.asList(instance.interfaceInfo.keySet().toArray());
        if (interfaceinfokey == null) return;
        //Arrays.sort(new List[]{interfaceinfokey});
        Collections.sort(interfaceinfokey, new SortByName());
        String beaninfo = "\n";
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < interfaceinfokey.size(); i++) {
            Bean b = instance.interfaceInfo.get(interfaceinfokey.get(i));
            buf.append("\t<bean id=\"" + b.id + "\" class=\"" + b.clazz + LastLnDex + "\"/>\n");
        }
        beaninfo = beaninfo + buf.toString();
        instance.BEANPATH = instance.basepath + instance.BEANPATH;
        instance.mkdirs(instance.BEANPATH);
        File file = new File(instance.BEANPATH + "spring-consumer.xml");
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file, false);
            PrintWriter pw = new PrintWriter(fileWriter, false);
            pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            StringBuilder beans = new StringBuilder();
            beans.append("<beans xmlns=\"http://www.springframework.org/schema/beans\"\n").
                    append("\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n").
                    append("\txmlns:context=\"http://www.springframework.org/schema/context\"\n").
                    append("\txmlns:tx=\"http://www.springframework.org/schema/tx\"\n").
                    append("\txmlns:aop=\"http://www.springframework.org/schema/aop\" xmlns:p=\"http://www.springframework.org/schema/p\"\n").
                    append("\txmlns:util=\"http://www.springframework.org/schema/util\"\n").
                    append("\txmlns:mvc=\"http://www.springframework.org/schema/mvc\"\n").
                    append("\txsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\n").
                    append("\thttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd\n").
                    append("\thttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd\n").
                    append("\thttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd\n").
                    append("\thttp://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd\"\n").
                    append("\tdefault-autowire=\"byName\">\n\n");
            pw.println(beans.toString());
            pw.println(beaninfo);
            pw.println("</beans>");
            pw.close();
        } catch (Exception e) {
            AegisLog.error(e);
        }
    }

    /**
     * parse method into Function Object
     *
     * @return Function
     */
    private Function parseMethod(Method method) {
        //function with final modified can not be overrided
        if (instance.parseModifier(method.getModifiers()).indexOf("public final") > -1) {
            return null;
        }

        Function func = new Function();
        func.method = method;
        func.name = method.getName();
        func.returns = instance.typeFormat(method.getGenericReturnType().toString());
        func.memberName = func.name + "ReturnValue";
        func.memberDefault = instance.defaultValue(func.returns);
        func.params = instance.getInfoParameters(method.getGenericParameterTypes());
        return func;
    }

    /**
     * parse mod, public、 final or others
     */
    private String parseModifier(int modifiers) {
        return Modifier.toString(modifiers);
    }

    /**
     * execute one interface-impl
     */
    private void generateSingleInterfaceImpl(String clsName, String version, String mockName) {
        Class<?> cls = instance.getClass(clsName);
        if (cls == null) return;

        //Method[] methods = cls.getDeclaredMethods();//all
        List<Method> methods = Arrays.asList(cls.getMethods());//not private
        if (methods == null) return;
        //Arrays.sort(new List[]{methods});
        //Collections.sort(methods, new SortByName());
        Map<String, Function> funcInfo = new LinkedHashMap<String, Function>();
        ArrayList<String> memberToDo = new ArrayList<>();
        for (int i = 0; i < methods.size(); i++) {
            Method method = methods.get(i);
            Function func = instance.parseMethod(method);
            if (func == null) continue;
            if (funcInfo.containsKey(func.memberName)) {
                memberToDo.add(func.memberName);
                func.memberName = instance.renameFunc(method);
            }
            funcInfo.put(func.memberName, func);
        }
        if (memberToDo != null) {
            for (String key : memberToDo) {
                if (funcInfo.containsKey(key)) {
                    Function function = funcInfo.get(key);
                    funcInfo.remove(key);
                    function.memberName = instance.renameFunc(function.method);
                    funcInfo.put(function.memberName, function);
                }
            }
        }

        instance.makeImplFile(cls, funcInfo, version, mockName);
    }

    private synchronized StringBuffer generateSingleInterfaceStringImpl(String clsName, String version, String mockName) {
        Class<?> cls = instance.getClass(clsName);
        if (cls == null) return null;

        //Method[] methods = cls.getDeclaredMethods();//all
        List<Method> methods = Arrays.asList(cls.getMethods());//not private
        if (methods == null) return null;
        //Arrays.sort(new List[]{methods});
        //Collections.sort(methods, new SortByName());
        Map<String, Function> funcInfo = new LinkedHashMap<String, Function>();
        ArrayList<String> memberToDo = new ArrayList<>();
        for (int i = 0; i < methods.size(); i++) {
            Method method = methods.get(i);
            Function func = instance.parseMethod(method);
            if (func == null) continue;
            if (funcInfo.containsKey(func.memberName)) {
                memberToDo.add(func.memberName);
                func.memberName = instance.renameFunc(method);
            }
            funcInfo.put(func.memberName, func);
        }
        if (memberToDo != null) {
            for (String key : memberToDo) {
                if (funcInfo.containsKey(key)) {
                    Function function = funcInfo.get(key);
                    funcInfo.remove(key);
                    function.memberName = instance.renameFunc(function.method);
                    funcInfo.put(function.memberName, function);
                }
            }
        }

        StringBuffer s = instance.makeImplStringTypeClass(cls, funcInfo, version, mockName);
        return s;
    }

    /**
     * make file: interfaceImpl or mockClass 不支持泛型
     */
    @Deprecated
    private void makeImplFileOld(Class<?> clazz, Map<String, Function> funcInfo, String version) {
        String clsName = clazz.getName();
        String interfaceName = clsName;
        String mockName = "Mock" + clsName.substring(clsName.lastIndexOf('.') + 1);

        Iterator<Map.Entry<String, Function>> entries = funcInfo.entrySet().iterator();
        StringBuilder meminfo = new StringBuilder("\n");
        StringBuilder funinfo = new StringBuilder("\n");
        StringBuilder setinfo = new StringBuilder("\n");
        StringBuilder clearinfo = new StringBuilder("\n");
        clearinfo.append("\tpublic static void clear() {\n");
        meminfo.append("\tpublic static ").append(mockName).append(" instance = new ").append(mockName).append("();\n\n");

        List<Object> funckey = Arrays.asList(funcInfo.keySet().toArray());
        Collections.sort(funckey, new SortByName());
        //Arrays.sort(new List[]{funckey});
        String modify = "";
        for (int i = 0; i < funckey.size(); i++) {
            //System.out.println(funckey.get(i));
            Function f = funcInfo.get(funckey.get(i));
            modify = parseModifier(f.method.getModifiers());
            if (f.returns.equals("void")) {
                funinfo.append("\t ").append(modify).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
                        append("){}\n\n");
            } else {
                funinfo.append("\t ").append(modify).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
                        append("){\n\t\treturn instance.").append(f.memberName).append(";\n\t}\n\n");
                meminfo.append("\tprivate ").append(f.returns).append(" ").append(f.memberName).append(" = ").
                        append(f.memberDefault).append(";\n");
                setinfo.append("\tpublic static void set").
                        append(f.memberName.substring(0, 1).toUpperCase() + f.memberName.substring(1)).
                        append("(").append(f.returns).append(" p){\n\t\tinstance.").append(f.memberName).append(" = p;\n\t}\n\n");
                clearinfo.append("\t\tinstance.").append(f.memberName).append(" = ").append(f.memberDefault).append(";\n");
            }
        }
        clearinfo.append("\t}\n\n");

        File file = new File(instance.resPath + mockName + ".java");
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file, false);
            PrintWriter pw = new PrintWriter(fileWriter, false);
            pw.println("package " + instance.packageName + ";\n");
            if (instance.isInterface(clazz)) {
                pw.println("public class " + mockName + " implements " + interfaceName + "{\n");
            } else {
                pw.println("public class " + mockName + " extends " + interfaceName + "{\n");
            }
            pw.println("\tpublic static String VERSION = \"" + version + "\";\n");
            pw.println(meminfo.toString());
            pw.println(funinfo.toString());
            pw.println(setinfo.toString());
            pw.println(clearinfo.toString());
            pw.println("}");
            pw.close();
        } catch (Exception e) {
            AegisLog.error(e);
        }
    }

    /**
     * 【代码留存】 map方式解决并发冲突问题，现在用threadlocal，暂时未使用（//TODO） 非引用类型转换（map不能直接存java自带的原有类型）
     */
    @Deprecated
    private String primitiveTypeChange(String type) {
        String res = null;
        switch (type) {
            case "int":
                res = "Integer";
                break;
            case "byte":
                res = "Byte";
                break;
            case "short":
                res = "Short";
                break;
            case "char":
                res = "Char";
                break;
            case "long":
                res = "Long";
                break;
            case "float":
                res = "Float";
                break;
            case "double":
                res = "Double";
                break;
            case "boolean":
                res = "Boolean";
                break;
            default:
                res = type;
                break;
        }
        return res;
    }

    /**
     * 【代码留存】 map方式解决并发冲突问题，现在用threadlocal，暂时未使用（//TODO）
     */
    @Deprecated
    private void makeImplFileBackup(Class<?> clazz, Map<String, Function> funcInfo, String version, String mockName) {
        String clsName = clazz.getName();
        String interfaceName = clsName;
        if (mockName.equals("")) {
            mockName = "Mock" + clsName.substring(clsName.lastIndexOf('.') + 1);
        } else {
            mockName = mockName.substring(mockName.lastIndexOf(".") + 1);
        }

        StringBuilder meminfo = new StringBuilder("\n");
        StringBuilder funinfo = new StringBuilder("\n");
        StringBuilder setinfo = new StringBuilder("\n");
        StringBuilder clearinfo = new StringBuilder("\n");
        clearinfo.append("\tpublic static void clear() {\n");
        meminfo.append("\tpublic static ").append(mockName).append(" instance = new ").append(mockName).append("();\n\n");

        List<Object> funckey = Arrays.asList(funcInfo.keySet().toArray());
        Collections.sort(funckey, new SortByName());

        String modifyInfo = "";
        String returnInfoF = "";
        boolean hasT = false;
        for (int i = 0; i < funckey.size(); i++) {
            Function f = funcInfo.get(funckey.get(i));

            Pattern p = Pattern.compile("\\WT\\W");
            Matcher m1 = p.matcher(f.params);
            Matcher m2 = p.matcher(f.returns);
            if (m1.find() || m2.find()) {
                modifyInfo = " <T>";
                hasT = true;
            } else {
                modifyInfo = "";
            }

            if (f.returns.equals("void")) {
                funinfo.append("\tpublic").append(modifyInfo).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
                        append("){}\n\n");
            } else {
                if (f.returns.equals("T")) {
                    returnInfoF = "return (T) instance.";
                    modifyInfo = " <T>";
                } else {
                    returnInfoF = "return instance.";
                }
                funinfo.append("\tpublic").append(modifyInfo).append(" ").append(f.returns).append(" ").append(f.name).append("(").append(f.params).
                        append("){\n\t\tString key = getKey();\n\t\t").append(f.returns).append(" res = ").append(f.memberDefault).append(";\n\t\t").
                        append("if(instance.").append(f.memberName).append(".containsKey(key)").append(") {\n\t\t\tres = ").
                        append(returnInfoF.replace("return ", "")).append(f.memberName).append(".get(key);\n\t\t\t").
                        append("instance.").append(f.memberName).append(".remove(key);\n\t\t").append("}\n\t\t").
                        append("return res;\n\t}\n\n");
                meminfo.append("\tprivate ").append("Map<String, ").append(primitiveTypeChange(f.returns)).append("> ").append(f.memberName).append(" = ").append("new HashMap<>();\n");
                setinfo.append("\tpublic static").append(modifyInfo).append(" void set").
                        append(f.memberName.substring(0, 1).toUpperCase() + f.memberName.substring(1)).
                        append("(").append(f.returns).append(" p){\n\t\tinstance.").append(f.memberName).append(".put(getKey(), p);\n\t}\n\n");
                clearinfo.append("\t\tinstance.").append(f.memberName).append(".clear();\n");
            }
        }
        clearinfo.append("\t}\n\n");

        ///
        StringBuilder keyinfo = new StringBuilder("\n");
        keyinfo.append("\tprivate static String getKey(){\n").
                append("\t\tStackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();\n").
                append("\t\tString key = \"default\";\n").
                append("\t\tint index = -1;\n\n").
                append("\t\tfor(StackTraceElement e : stackTraceElements) {\n").
                append("\t\t\tindex++;\n").
                append("\t\t\tif(e.getClassName().equals(\"sun.reflect.NativeMethodAccessorImpl\") && e.getMethodName().equals(\"invoke0\")) {\n").
                append("\t\t\t\tbreak;\n").
                append("\t\t\t}\n").
                append("\t\t}\n").
                append("\t\tif(index > 0 && index != stackTraceElements.length){\n").
                append("\t\t\tStackTraceElement e = stackTraceElements[--index];\n").
                append("\t\t\tkey = e.getClassName() + \"-\" + e.getMethodName();\n").
                append("\t\t}\n").
                append("\t\treturn key;\n").
                append("\t}\n");

        File file = new File(instance.resPath + mockName + ".java");
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file, false);
            PrintWriter pw = new PrintWriter(fileWriter, false);
            pw.println("package " + instance.packageName + ";\n");
            ///
            pw.println("\nimport java.util.*;\n");
            if (hasT) {
                mockName += "<T>";
            }
            if (instance.isInterface(clazz)) {
                pw.println("public class " + mockName + " implements " + interfaceName + "{\n");
            } else {
                pw.println("public class " + mockName + " extends " + interfaceName + "{\n");
            }
            pw.println("\tpublic static String VERSION = \"" + version + "\";\n");
            pw.println(meminfo.toString());
            pw.println(funinfo.toString());
            pw.println(setinfo.toString());
            pw.println(clearinfo.toString());
            pw.println(keyinfo.toString());
            pw.println("}");
            pw.close();
        } catch (Exception e) {
            AegisLog.error(e);
        }
    }

}
